/*
 * 2011 Runedev development team
 * http://lazygamerz.org
 *
 * This library is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Lesser Gereral Public Licence as published by the Free
 * Software Foundation; either version 3 of the Licence, or (at your opinion) any
 * later version.
 *
 * This library is distributed in the hope that it will be usefull, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of merchantability or fitness for a
 * particular purpose. See the GNU Lesser General Public Licence for more details.
 *
 * You should have received a copy of the GNU Lesser General Public Licence along
 * with this library; if not, write to the Free Software Foundation, Inc., 59
 * Temple Place, Suite 330, Boston, Ma 02111-1307 USA.
 *
 * http://www.gnu.org/licenses/lgpl.html (English)
 * http://gugs.sindominio.net/gnu-gpl/lgpl-es.html (Espa�ol)
 *
 */
package org.lazygamerz.scripting.api;

import java.applet.Applet;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;

import org.rsbot.bot.Bot;
import org.rsbot.client.Client;

/**
 * Keyboard related operations.
 * @author Runedev development team. - version 1.0
 */
public class Keyboard {
    private final java.util.Random random = new java.util.Random();
    
	public Keyboard() {
	}

	/**
	 * Presses and releases a given key.
	 *
	 * @param c The character to press.
	 */
	public void sendKey(final char c) {
		sendKey(c, 0);
	}
	
    private void sendKey(final char ch, final int delay) {
        boolean shift = false;
        Client client = Bot.getClient();
        
        int code = ch;
        if ((ch >= 'a') && (ch <= 'z')) {
            code -= 32;
        } else if ((ch >= 'A') && (ch <= 'Z')) {
            shift = true;
        }
        KeyEvent ke;
        if ((code == KeyEvent.VK_LEFT) || (code == KeyEvent.VK_UP) || (code == KeyEvent.VK_UP) || (code == KeyEvent.VK_DOWN)) {
            ke = new KeyEvent(getTarget(), KeyEvent.KEY_PRESSED, System.currentTimeMillis() + delay, 0, code, getKeyChar(ch), KeyEvent.KEY_LOCATION_STANDARD);
            client.getKeyboard()._keyPressed(ke);
            final int delay2 = random(50, 120) + random(0, 100);
            ke = new KeyEvent(getTarget(), KeyEvent.KEY_RELEASED, System.currentTimeMillis() + delay2, 0, code, getKeyChar(ch), KeyEvent.KEY_LOCATION_STANDARD);
            client.getKeyboard()._keyReleased(ke);
        } else {
            if (!shift) {
                ke = new KeyEvent(getTarget(), KeyEvent.KEY_PRESSED, System.currentTimeMillis() + delay, 0, code, getKeyChar(ch), KeyEvent.KEY_LOCATION_STANDARD);
                client.getKeyboard()._keyPressed(ke);
                /* Event Typed*/
                ke = new KeyEvent(getTarget(), KeyEvent.KEY_TYPED, System.currentTimeMillis() + 0, 0, 0, ch, 0);
                client.getKeyboard()._keyTyped(ke);
                /* Event Released*/
                final int delay2 = random(50, 120) + random(0, 100);
                ke = new KeyEvent(getTarget(), KeyEvent.KEY_RELEASED, System.currentTimeMillis() + delay2, 0, code, getKeyChar(ch), KeyEvent.KEY_LOCATION_STANDARD);
                client.getKeyboard()._keyReleased(ke);
            } else {
                /* Event Pressed for shift key*/
                final int s1 = random(25, 60) + random(0, 50);
                ke = new KeyEvent(getTarget(), KeyEvent.KEY_PRESSED, System.currentTimeMillis() + s1, InputEvent.SHIFT_DOWN_MASK, KeyEvent.VK_SHIFT, (char) KeyEvent.VK_UNDEFINED, KeyEvent.KEY_LOCATION_LEFT);
                client.getKeyboard()._keyPressed(ke);

                /* Event Pressed for char to send*/
                ke = new KeyEvent(getTarget(), KeyEvent.KEY_PRESSED, System.currentTimeMillis() + delay, InputEvent.SHIFT_DOWN_MASK, code, getKeyChar(ch), KeyEvent.KEY_LOCATION_STANDARD);
                client.getKeyboard()._keyPressed(ke);
                /* Event Typed for char to send*/
                ke = new KeyEvent(getTarget(), KeyEvent.KEY_TYPED, System.currentTimeMillis() + 0, InputEvent.SHIFT_DOWN_MASK, 0, ch, 0);
                client.getKeyboard()._keyTyped(ke);
                /* Event Released for char to send*/
                final int delay2 = random(50, 120) + random(0, 100);
                ke = new KeyEvent(getTarget(), KeyEvent.KEY_RELEASED, System.currentTimeMillis() + delay2, InputEvent.SHIFT_DOWN_MASK, code, getKeyChar(ch), KeyEvent.KEY_LOCATION_STANDARD);
                client.getKeyboard()._keyReleased(ke);

                /* Event Released for shift key*/
                final int s2 = random(25, 60) + random(0, 50);
                ke = new KeyEvent(getTarget(), KeyEvent.KEY_RELEASED, System.currentTimeMillis() + s2, InputEvent.SHIFT_DOWN_MASK, KeyEvent.VK_SHIFT, (char) KeyEvent.VK_UNDEFINED, KeyEvent.KEY_LOCATION_LEFT);
                client.getKeyboard()._keyReleased(ke);
            }
        }
    }

	/**
	 * Types a given string.
	 *
	 * @param text       The text to press/send.
	 * @param pressEnter <tt>true</tt> to press enter after pressing the text.
	 */
	public void sendText(final String text, final boolean pressEnter) {	
		sendText(text, pressEnter, 100, 200);
	}
	

    public void sendText(final String text, final boolean pressEnter, final int delay) {
        sendText(text, pressEnter, delay, delay);
    }

    public void sendText(final String text, final boolean pressEnter, final int minDelay, final int maxDelay) {
        final char[] chs = text.toCharArray();
        for (final char element : chs) {
            sendKey(element, random(minDelay, maxDelay));
            sleepNoException(random(minDelay, maxDelay));
        }
        if (pressEnter) {
            sendKey((char) KeyEvent.VK_ENTER, random(minDelay, maxDelay));
        }
    }

	/**
	 * Types a given string instantly.
	 *
	 * @param text       The text to press/send.
	 * @param pressEnter <tt>true</tt> to press enter after pressing the text.
	 */
	public void sendTextInstant(final String text, final boolean pressEnter) {
        for (final char c : text.toCharArray()) {
            sendKey(c, 0);
        }
        if (pressEnter) {
            sendKey((char) KeyEvent.VK_ENTER, 0);
        }
	}

	/**
	 * Presses and holds a given key.
	 *
	 * @param c The character to press.
	 * @see #releaseKey(char)
	 */
	public void pressKey(final char c) {
        KeyEvent ke;
        ke = new KeyEvent(getTarget(), KeyEvent.KEY_PRESSED, System.currentTimeMillis(), 0, c, getKeyChar(c));
        Bot.getClient().getKeyboard()._keyPressed(ke);
	}

	/**
	 * Releases a given held key.
	 *
	 * @param c The character to release.
	 * @see #pressKey(char)
	 */
	public void releaseKey(final char c) {
        KeyEvent ke;
        ke = new KeyEvent(getTarget(), KeyEvent.KEY_RELEASED, System.currentTimeMillis(), InputEvent.ALT_DOWN_MASK, c, getKeyChar(c));
        Bot.getClient().getKeyboard()._keyReleased(ke);
	}

    private Applet getTarget() {
        return (Applet) Bot.getClient();
    }
    
    private char getKeyChar(final char c) {
        final int i = c;
        if ((i >= 36) && (i <= 40))  {
            return KeyEvent.VK_UNDEFINED;
        }
        else  {
            return c;
        }
    }
    
    /**
     * Returns a random double in the specified range with a Gaussian
     * distribution having a mean of the absolute value of the difference
     * between min and max and a standard deviation of 1.  The returned
     * value will honor the min and max boundaries.
     *
     * @param min   Minimum value (inclusive).
     * @param max   Maximum value (inclusive).
     * @return      The random <code>double</code> generated.
     */
    public double random(double min, double max) {
    	double mean = min + Math.abs(max-min)/2;

    	double next;
    	
    	// The loop ensures we honor the min/max values.
    	do  {
    		next = mean + random.nextGaussian();
    	} while (next<min || next>max);    	 
    	
        return next;
    }

    /**
     * Returns a random int in the specified range with a Gaussian
     * distribution having a mean of the absolute value of the difference
     * between min and max and a standard deviation of 1.  The returned
     * value will honor the min and max boundaries.
     *
     * @param min   Minimum value (inclusive).
     * @param max   Maximum value (inclusive).
     * @return      The random <code>int</code> generated.
     */
    public int random(int min, int max) {
    	int mean = min + Math.abs(max-min)/2;
    	int next;
    	
    	// The loop ensures we honor the min/max values.
    	do  {
    		next = (int) (mean + random.nextGaussian());
    	} while (next<min || next>max);    	 
    	
        return next;
    }

    /**
     * Returns a random int in the specified range with a Gaussian
     * distribution having a mean of the absolute value of the difference
     * between min and max and a standard deviation of sd.  The returned
     * value will honor the min and max boundaries.
     *
     * @param min   Minimum value (inclusive).
     * @param max   Maximum value (inclusive).
     * @return      The random <code>int</code> generated.
     */
    public int random(int min, int max, int sd) {
        int mean = min + Math.abs(max - min) / 2;
        int next;
        do {
        	next = (int) (mean + random.nextGaussian() * sd);
        } while (next < min || next > max);
        return next;
    }
    
    private void sleepNoException(final long t) {
        try {
            Thread.sleep(t);
        } catch (final Exception e) {
        }
    }
}
